﻿#region Imports

using System.Collections;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Text;
using Remotion.Linq.Clauses;
using Remotion.Linq.Utilities;
using Remotion.Linq.Clauses.Expressions;
using System;

#endregion

namespace CITPRO.Linq.LinqToOdbc.SqlQueryGeneration {


    internal class QueryPartsAggregator {
        public QueryPartsState State { get; set; }

        private class SelectColumn {
            public Type Entity { get; set; }
            public string TargetProperty { get; set; }
        }

        private class FromTable {
            public Type Entity { get; set; }
            public string TableName { get; set; }
            public string Alias { get; set; }
        }

        private readonly List<FromTable> fromTables = new List<FromTable>();
        private readonly List<SelectColumn> selectColumns;
        private readonly StringBuilder whereClause;

        public QueryPartsAggregator(QueryPartsAggregator parentAggregator = null) {
            selectColumns = new List<SelectColumn>();
            whereClause = new StringBuilder();
            State = QueryPartsState.Initialized;

            if (parentAggregator != null) {
                fromTables.AddRange(parentAggregator.fromTables);
            }
        }

        internal string BuildSqlString() {
            var sql = new StringBuilder();
            if (State == QueryPartsState.VisitingInClause) {
                sql.AppendLine(ResolveSelectColumnText());
                sql.AppendLine(whereClause.ToString());
            } else {
                sql.AppendLine("SELECT");
                sql.AppendLine(ResolveSelectColumnText());
                sql.AppendLine("FROM");
                sql.AppendLine(ResolveFromTableText());
                if (whereClause.Length > 0) {
                    sql.AppendLine("WHERE");
                    sql.AppendLine(whereClause.ToString());
                }
                sql = sql.Replace("= " + Null, "IS " + Null);
                sql = sql.Replace("<> " + Null, "IS NOT " + Null);
            }
            return sql.ToString().TrimEnd();
        }

        private string ResolveFromTableText() {
            var clauses = new List<string>();
            const string alias = "{0} {1}";
            const string noAlias = "{0}";
            foreach (var item in fromTables) {
                if (item.Alias == null) {
                    clauses.Add(string.Format(noAlias, item.TableName));
                } else {
                    clauses.Add(string.Format(alias, item.TableName, item.Alias));
                }
            }

            return clauses.ToCommaNewlineSeperatedList();
        }

        private string ResolveSelectColumnText() {
            const string format = "{0}.{1}";
            var result = new List<string>();
            foreach (var sc in selectColumns) {
                if (sc.Entity != null) {
                    var tableRef = ResolveTableReference(sc);
                    if (tableRef != null) {
                        result.Add(string.Format(format, tableRef, sc.TargetProperty));
                    }
                } else {
                    result.Add(sc.TargetProperty);
                }
            }

            //return selectColumns
            //    .Select(c => {
            //        if (c.Entity != null) {

            //            return string.Format(format, ResolveTableReference(c), c.TargetProperty);
            //        } else
            //            return c.TargetProperty;
            //    })
            //    .ToCommaNewlineSeperatedList();

            return result.ToCommaNewlineSeperatedList();
        }

        private string ResolveTableReference(SelectColumn c) {
            var table = fromTables.FirstOrDefault(t => t.Entity == c.Entity);
            if (table != null)
                return table.Alias ?? table.TableName;
            else
                return null;
        }

        internal void AddFromTable(Type entity, string alias) {
            var existing = fromTables.SingleOrDefault(t => t.Entity == entity && t.Alias == alias);
            if (existing != null) return;

            fromTables.Add(new FromTable {
                Alias = alias,
                Entity = entity,
                TableName = ResolveTableName(entity)
            });
        }

        private string ResolveTableName(Type entity) {
            var tableAttribute = entity.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault();
            return tableAttribute == null ? entity.Name : ((TableAttribute)tableAttribute).Name;
        }


        internal void AddToWhere(string text) {
            whereClause.AppendFormat(" {0}", text);
        }

        internal void AddMemberExpression(Type entity, string propertyName) {
            switch (State) {
                case QueryPartsState.VisitingMainFromClause:
                    break;
                case QueryPartsState.VisitingSelectClause:
                case QueryPartsState.VisitingInClause:
                    selectColumns.Add(new SelectColumn {
                        Entity = entity,
                        TargetProperty = propertyName
                    });
                    break;
                case QueryPartsState.VisitingWhereClause:
                    AddToWhere(ResolveMemberText(entity, propertyName));
                    break;
                default:
                    throw new NotImplementedException();
            }
        }


        internal void AddMemberExpression(Type type) {
            foreach (var property in type.GetProperties()) {
                selectColumns.Add(new SelectColumn {
                    Entity = type,
                    TargetProperty = property.Name
                });
            }

            //selectColumns.Add(new SelectColumn {
            //    Entity = type,
            //    TargetProperty = "*"
            //});
        }

        private string ResolveMemberText(Type entity, string propertyName) {
            var table = fromTables.First(t => t.Entity == entity);
            return string.Format("{0}.{1}", table.Alias ?? table.TableName, propertyName);
        }

        private const string Null = "NULL";

        internal void AddConstantExpression(Type type, object value) {
            switch (State) {
                case QueryPartsState.VisitingWhereClause:
                    if (value == null) {
                        AddToWhere(Null);
                    } else if (type.IsValueType) {
                        AddToWhere(value.ToString());
                    } else {
                        AddToWhere(string.Format("'{0}'", value));
                    }
                    break;
                case QueryPartsState.VisitingInClause:
                    var array = (IEnumerable)value;
                    string[] stringArray;
                    if (type.GetElementType().IsValueType) {
                        stringArray = array.Cast<object>()
                            .Select(s => s.ToString())
                            .ToArray();
                    } else {
                        stringArray = array.Cast<object>()
                            .Select(s => string.Format("'{0}'", s.ToString()))
                            .ToArray();
                    }

                    AddToWhere(string.Format(" IN ({0})", string.Join(",", stringArray)));
                    break;
                case QueryPartsState.VisitingSelectClause:
                    if (type.IsValueType) {
                        selectColumns.Add(new SelectColumn {
                            Entity = null,
                            TargetProperty = value.ToString()
                        });
                    } else {
                        selectColumns.Add(new SelectColumn {
                            Entity = null,
                            TargetProperty = string.Format("'{0}'", value.ToString())
                        });
                    }
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        //private static readonly ICollection<Type> numericTypes = new[]{
        //    typeof(Int16),
        //    typeof(Int32),
        //    typeof(Int64),
        //    typeof(short),
        //    typeof(ushort),
        //    typeof(int),
        //    typeof(uint),
        //    typeof(long),
        //    typeof(ulong),
        //    typeof(float),
        //    typeof(double),
        //    typeof(decimal)
        //};

        //private static bool IsNumericType(object value) {
        //    return numericTypes.Contains(value.GetType());
        //}


        internal void AddLeftSideContainer() {
            AddToWhere("(");
        }

        internal void AddRightSideContainer() {
            AddToWhere(")");
        }

        internal void AddOperator(string operatorText) {
            AddToWhere(operatorText);
        }
    }
}